#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/stat.h>
#include <dirent.h>

#include "../lib/vector.h"

enum ERROR_CODE {
    ERR_PARAM_COUNT_NOT_LEGAL = -101,
    ERR_OPT_NOT_LEGAL = -102,
    ERR_S_PARAM_NOT_LEGAL = -103,
    ERR_FILE_OR_DIR_NEEDED = -104,
    ERR_DOUBLE_S_OPT = -105,
    ERR_FILE_OR_DIR_NOT_EXIST = -201,
    ERR_FILE_OPEN_FAILED = -202,
    ERR_DIR_OPEN_FAILED = -203,
    ERR_DIRFD = -204,
    ERR_COUNT_ZERO = -301,
};

// options
enum OPTION {
    OPT_h,
    OPT_A,
    OPT_b,
    OPT_r,
    OPT_s,
    OPT_NUM
};
int g_option[OPT_NUM];

Vector g_dirPath;
int count;
char *suffix; // 后缀

int **line_printed;
const char **path_printed;

void print_help();

int is_suffix_legal(const char *suf);

int is_dir(const char *path);

void get_option(const char *strOption);

void get_path(const char *strPath);

void do_lc(const char *path);

int *compute_line_number(const char *path);

void print();

char *substr(const char *str, int offset, int length);

char *append(const char *str1, const char *str2);

int has_suffix(const char *str, char *sub);

int main(int argc, char *argv[]) {
    // 初始化全局变量
    memset(g_option, 0, sizeof(int) * OPT_NUM);
    vector_setup(&g_dirPath, 10, 100 * sizeof(char *)); // 字符串长度设定为800
    count = 0;
    suffix = "";

    line_printed = (int **) malloc(sizeof(int *) * 100);
    path_printed = (const char **) malloc(sizeof(char *) * 100);
    for (int i = 0; i < 100; i++) {
        line_printed[i] = (int *) malloc(sizeof(int *) * 3);
        path_printed[i] = (const char *) malloc(sizeof(char *) * 3);
    }

    // 第一个参数是a.out
    if (argc < 2) {
        print_help();
        exit(ERR_PARAM_COUNT_NOT_LEGAL);
    }

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-s") == 0) {
            if (g_option[OPT_s]) {
                fprintf(stdout, "-s can only exist one");
                exit(ERR_DOUBLE_S_OPT);
            }
            // -s 获取后一个参数
            if (i + 1 >= argc) {
                print_help();
                exit(ERR_S_PARAM_NOT_LEGAL);
            }

            if (!is_suffix_legal(argv[i + 1])) {
                fprintf(stdout, "%s: suffix not legal", argv[i + 1]);
                exit(ERR_S_PARAM_NOT_LEGAL);
            }

            g_option[OPT_s] = 1;
            suffix = argv[++i];
        } else if (argv[i][0] == '-') {
            get_option(argv[i]);
        } else {
            get_path(argv[i]);
        }
    }

    if (g_option[OPT_h] || vector_is_empty(&g_dirPath)) {
        print_help();
        exit(ERR_FILE_OR_DIR_NEEDED);
    }

    Iterator iterator = vector_begin(&g_dirPath);
    Iterator last = vector_end(&g_dirPath);
    for (; !iterator_equals(&iterator, &last); iterator_increment(&iterator)) {
        do_lc((char *) iterator_get(&iterator));
    }

    print();

    // 释放内存
    vector_destroy(&g_dirPath);
    for (int i = 0; i < 100; i++) {
        free(line_printed[i]);
    }
    free(line_printed);
    return 0;
}

// 打印帮助
void print_help() {
    fprintf(stdout, "Usage: lc [OPTION] ... [FILE | DIR] ...\n"
                    "Print newline counts for each FILE or DIR, and a total line if more than one FILE is specified.\n"
                    "\n"
                    "With no FILE, or when FILE is -, read standard input.\n"
                    "\n"
                    "The options below may be used to control print msg\n"
                    "    -A, --all                     print the total line number only\n"
                    "    -b                            ignore the Blank line when counting\n"
                    "    -r                            count directory Recursively\n"
                    "    -s, --suffix=FILE-SUFFIX    only count the file with specified Suffix\n"
                    "    -h, --help                    display this Help and exit");
}


int is_dir(const char *pathname) {
    assert(pathname);
    struct stat buf;
    int result = stat(pathname, &buf);
    if (result != 0) {
        // 文件不存在
        fprintf(stdout, "%s: No this file or directory", pathname);
        exit(ERR_FILE_OR_DIR_NOT_EXIST);
    }

    if (S_IFDIR & buf.st_mode) { // 文件夹
        return 1;
    } else if (S_IFREG & buf.st_mode) { // 文件
        return 0;
    }

    return -1;
}

int is_suffix_legal(const char *suf) {
    if (strlen(suf) > 255) {
        return 0;
    }

    for (int i = 0; i < strlen(suf); i++) {
        switch (suf[i]) {
            case '<':
            case '>':
            case '/':
            case '\\':
            case '|':
            case ':':
            case '\"':
            case '*':
            case '?':
                return 0;
            default:
                continue;
        }
    }

    return 1;
}

void get_option(const char *strOption) {
    assert(strOption);
    if (strcmp(strOption, "-h") == 0 || strcmp(strOption, "--help") == 0) {
        g_option[OPT_h] = 1;
    } else if (strcmp(strOption, "-A") == 0 || strcmp(strOption, "--all") == 0) {
        g_option[OPT_A] = 1;
    } else if (strcmp(strOption, "-b") == 0) {
        g_option[OPT_b] = 1;
    } else if (strcmp(strOption, "-r") == 0) {
        g_option[OPT_r] = 1;
    } else if (strstr(strOption, "--suffix") != NULL) {
        if (g_option[OPT_s]) {
            fprintf(stdout, "--suffix and -s can only exist one");
            exit(ERR_DOUBLE_S_OPT);
        }

        suffix = substr(strOption, 9, strlen(strOption) - 9);
        if (!is_suffix_legal(suffix)) {
            fprintf(stdout, "%s: suffix not legal", suffix);
            exit(ERR_S_PARAM_NOT_LEGAL);
        }

        g_option[OPT_s] = 1;
    } else {
        print_help();
        exit(ERR_OPT_NOT_LEGAL);
    }
}

void get_path(const char *strPath) {
    assert(strPath);
    vector_push_back(&g_dirPath, (void *) strPath);
}

int *compute_line_number(const char *path) {
    FILE *fp;
    // 判断是不是空文件
    if ((fp = fopen(path, "r")) == NULL) {
        perror(path);
        exit(ERR_FILE_OPEN_FAILED);
    }

    // 空行计数
    int null_count = 0;
    // 非空行计数
    int content_count = 0;


    char buff[1024] = {0};
    // 按行读取
    while (fgets(buff, sizeof(buff), fp)) {
        if (buff[0] == '\n' || buff[0] == '\r') {
            null_count++; // 空行
        } else {
            content_count++;
        }
    }
    // 总行数计数
    int total_count = content_count + null_count;
    // 关闭文件
    fclose(fp);

    int *arr = (int *) malloc(sizeof(int) * 3);
    arr[0] = content_count;
    arr[1] = null_count;
    arr[2] = total_count;
    return arr;
}

void do_lc(const char *path) {
    assert(path);

    DIR *p_dir;
    struct dirent *p_next;

    // 0: file; 1:folder;
    int flag = is_dir(path);
    if (flag == 0) {
        // -s 只要指定后缀
        if (g_option[OPT_s] && !has_suffix(path, suffix)) {
            return;
        }

        // 文件
        int *lines = compute_line_number(path);
        const char *pathname = path;

        line_printed[count] = lines;
        path_printed[count] = pathname;
        count++;
    } else if (flag == 1) {
        // 文件夹
        if ((p_dir = opendir(path)) == NULL) {
            perror(path);
            exit(ERR_DIR_OPEN_FAILED);
        }

        if (dirfd(p_dir) == -1) {
            perror("dirfd error");
            exit(ERR_DIRFD);
        }

        Vector list;
        vector_setup(&list, 10, 100 * sizeof(char *));

        while ((p_next = readdir(p_dir)) != NULL) {
            const char *p_cur = path;
            char *connector = (*(p_cur + (strlen(p_cur) - 1)) == '/') ? "" : "/";
            char *pathname = append(append(p_cur, connector), p_next->d_name);

            if (is_dir(pathname) == 1) {
                // 当不采用-r模式时，所有文件夹都跳过
                if (g_option[OPT_r] == 0) {
                    continue;
                }
                // 当采用-r模式时，跳过"."和".."之外的文件夹
                if (strcmp(p_next->d_name, ".") == 0 || strcmp(p_next->d_name, "..") == 0) {
                    continue;
                }
            }
            vector_push_back(&list, (void *) pathname);
        }

        closedir(p_dir);

        Iterator iterator = vector_begin(&list);
        Iterator last = vector_end(&list);
        for (; !iterator_equals(&iterator, &last); iterator_increment(&iterator)) {
            do_lc((char *) iterator_get(&iterator));
        }

        vector_destroy(&list);
    }
}

void print() {
    // 输出全部行数
    int mode = 2;
    if (g_option[OPT_b]) {
        // -b 忽略空行
        mode = 0;
    }

    if (count == 0) {
        print_help();
        exit(ERR_COUNT_ZERO);
    } else {
        fprintf(stdout, "%4s\t%-4s\n", "line", "file");
        fprintf(stdout, "%4s\t%-4s\n", "----", "----");
        // 只有一个文件的时候不起作用
        if (g_option[OPT_A] && !(count == 1 && is_dir(path_printed[0]) == 0)) {
            // -A --all 只输出总行数
            int total = 0;
            for (int i = 0; i < count; i++) {
                total += line_printed[i][mode];
            }
            fprintf(stdout, "%4d\ttotal\n", total);
        } else {
            int total = 0;
            for (int i = 0; i < count; i++) {
                fprintf(stdout, "%4d\t%s\n", line_printed[i][mode], path_printed[i]);
                total += line_printed[i][mode];
            }
            if (count > 1) {
                fprintf(stdout, "%4d\ttotal\n", total);
            }
        }

        fprintf(stdout, "\n");
    }
}

// 子字符串
char *substr(const char *str, int offset, int length) {
    assert(length > 0);
    int total_length = strlen(str);
    int real_length = ((total_length - offset) >= length ? length : (total_length - offset)) + 1;
    char *res;
    if (NULL == (res = (char *) malloc(real_length * sizeof(char)))) {
        exit(-1);
    }
    strncpy(res, str + offset, real_length - 1);
    res[real_length - 1] = '\0';
    return res;
}

// 拼接字符串
char *append(const char *str1, const char *str2) {
    char *buffer = (char *) malloc(strlen(str1) + strlen(str2) + 1);
    strcpy(buffer, str1);
    strcat(buffer, str2);
    return buffer;
}

// str后缀是否为sub
int has_suffix(const char *str, char *sub) {
    int lenStr = strlen(str), lenSub = strlen(sub);
    assert(lenStr >= lenSub);
    const char *p = str + (lenStr - 1), *q = sub + (lenSub - 1);
    for (int i = 0; i < lenSub; i++) {
        if (*p != *q) {
            return 0;
        }
        p--;
        q--;
    }

    // 兼容sub含有.的情况
    if (*(p + 1) == '.' && *(q + 1) == '.') {
        return 1;
    }

    return (*p != '.') ? 0 : 1;
}